﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace Trentacular.SharePoint.Util
{
    public delegate void SPSiteAction(SPSite site);
    public delegate void SPWebAction(SPWeb web);
    public delegate void SPListAction(SPList list);

    public static class SPUtils
    {
        #region Feature Helpers

        public static bool IsActivated(this SPFeatureCollection features, Guid featureId)
        {
            return features.Cast<SPFeature>().FirstOrDefault(f => f.DefinitionId.Equals(featureId)) != null;
        }

        public static void EnsureActivated(this SPFeatureCollection features, Guid featureId)
        {
            if (!IsActivated(features, featureId))
                features.Add(featureId);
        }

        public static void EnsureDeactivated(this SPFeatureCollection features, Guid featureId)
        {
            if (IsActivated(features, featureId))
                features.Remove(featureId);
        }

        #endregion

        public static bool IsCentralAdmin(this SPWeb web)
        {
            return web.IsRootWeb &&
                web.WebTemplate == "CENTRALADMIN" &&
                web.Site.WebApplication.IsAdministrationWebApplication;
        }

        /// <summary>
        /// Performs an action using an SPWeb object that requires unsafe updates
        /// </summary>
        public static void DoUnsafeUpdate(this SPWeb web, Action action)
        {
            bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
            web.AllowUnsafeUpdates = true;
            action();
            web.AllowUnsafeUpdates = allowUnsafeUpdates;
        }

        #region Impersonation Helpers

        /// <summary>
        /// Performs an action using an SPSite object impersonating the SystemUser for the site
        /// </summary>
        public static void DoAsSystemUser(this SPSite site, SPSiteAction action)
        {
            var systemUser = site.SystemAccount;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                DoAsUser(site, systemUser, action);
            });
        }

        /// <summary>
        /// Performs an action using an SPWeb object impersonating the SystemUser for the site
        /// </summary>
        public static void DoAsSystemUser(this SPWeb web, SPWebAction action)
        {
            SPUser systemUser = web.Site.SystemAccount;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                DoAsUser(web, systemUser, action);
            });
        }

        /// <summary>
        /// Performs an action using an SPWeb object impersonating a given user and
        /// allowing unsafe updates
        /// </summary>
        public static void DoUnsafeUpdateAsSystemUser(this SPWeb web, SPWebAction action)
        {
            web.DoAsSystemUser(delegate(SPWeb systemWeb)
            {
                systemWeb.DoUnsafeUpdate(delegate()
                {
                    action(systemWeb);
                });
            });
        }

        /// <summary>
        /// Performs an action using an SPSite object impersonating a given user
        /// </summary>
        public static void DoAsUser(this SPSite site, SPUser user, SPSiteAction action)
        {
            DoAsUser(site, user, delegate(SPSite impersonatedSite, bool isNewSite)
            {
                action(impersonatedSite);
            });
        }

        /// <summary>
        /// Performs an action using an SPWeb object impersonating a given user
        /// </summary>
        public static void DoAsUser(this SPWeb web, SPUser user, SPWebAction action)
        {
            DoAsUser(web.Site, user, delegate(SPSite impersonatedSite, bool isNewSite)
            {
                if (!isNewSite)
                {
                    action(web);
                    return;
                }

                using (var impersonatedWeb = impersonatedSite.OpenWeb(web.ID))
                {
                    action(impersonatedWeb);
                }
            });
        }

        private delegate void SPSiteActionInternal(SPSite site, bool isNewSite);
        private static void DoAsUser(SPSite site, SPUser user, SPSiteActionInternal action)
        {
            // If we are already running as the given User Token, just pass the web along
            if (site.UserToken != null && site.UserToken.CompareUser(user.UserToken))
            {
                action(site, false);
                return;
            }

            using (var impersonatedSite = new SPSite(site.ID, user.UserToken))
            {
                action(site, true);
            }
        }

        #endregion

        #region PropertyBag Helpers

        /// <summary>
        /// Simply adds a key/value pair to the SPWeb AllProperties Hashtable and the
        /// Deprecated SPWeb Property bag without updating
        /// </summary>
        public static void AddProperty(this SPWeb web, string key, string value)
        {
            //bool existsInDeprecatedPropertyBag = !string.IsNullOrEmpty(web.Properties[key]);
            //bool existsInAllPropertiesHashtable = web.AllProperties.ContainsKey(key);
            //bool hasUpperChars = key.ToCharArray().FirstOrDefault(c => char.IsUpper(c)) != null;

            web.Properties[key] = value;
            web.AllProperties[key] = value;
        }

        /// <summary>
        /// Simply removes the property with the given key from the SPWeb AllProperties
        /// hashtable and the deprecated SPWeb property bag without updating
        /// </summary>
        public static void RemoveProperty(this SPWeb web, string key)
        {
            web.AllProperties.Remove(key);
            web.Properties[key] = null;
        }

        /// <summary>
        /// Adds the given key/value pair to both the SPWeb AllProperties Hashtable and
        /// the deprecated SPWeb Property Bag and Updates both the Property Bag and the SPWeb.
        /// This method ensures the operation is done with System permissions.
        /// </summary>
        public static void AddPropertyAndUpdate(this SPWeb web, string key, string value)
        {
            var properties = new Dictionary<string, string>(1);
            properties.Add(key, value);
            AddPropertiesAndUpdate(web, properties);
        }

        /// <summary>
        /// Adds the given key/value pairs to both the SPWeb AllProperties Hashtable and
        /// the deprecated SPWeb Property Bag and Updates both the Property Bag and the SPWeb.
        /// This method ensures the operation is done with System permissions.
        /// </summary>
        public static void AddPropertiesAndUpdate(this SPWeb web, IDictionary<string, string> properties)
        {
            DoUnsafeUpdateAsSystemUser(web, delegate(SPWeb systemWeb)
            {
                foreach (var pair in properties)
                {
                    AddProperty(systemWeb, pair.Key, pair.Value);
                }

                systemWeb.Update();
                systemWeb.Properties.Update();
            });
        }

        /// <summary>
        /// Removes the property with the given key from both the SPWeb AllProperties
        /// hashtable and the deprecated SPWeb Property Bag and updates both the Property Bag
        /// and the SPWeb.  This method ensures the operation is done with System permissions.
        /// </summary>
        public static void RemovePropertyAndUpdate(this SPWeb web, string key)
        {
            RemovePropertiesAndUpdate(web, key);
        }

        /// <summary>
        /// Removes the properties with the given keys from both the SPWeb AllProperties
        /// hashtable and the deprecated SPWeb Property Bag and updates both the Property Bag
        /// and the SPWeb.  This method ensures the operation is done with System permissions.
        /// </summary>
        public static void RemovePropertiesAndUpdate(this SPWeb web, params string[] keys)
        {
            DoUnsafeUpdateAsSystemUser(web, delegate(SPWeb systemWeb)
            {
                foreach (var key in keys)
                {
                    RemoveProperty(systemWeb, key);
                }

                systemWeb.Update();
                systemWeb.Properties.Update();
            });
        }

        /// <summary>
        /// Safely gets a property value from the SPWeb Property Bag
        /// </summary>
        public static string GetPropertyValue(this SPWeb web, string key)
        {
            if (web.AllProperties.ContainsKey(key))
                return web.AllProperties[key].ToString();

            return null;
        }

        /// <summary>
        /// Reads a property value from the Farms property bag with elevated priveleges
        /// </summary>
        public static string GetFarmProperty(string key)
        {
            return GetFarmProperty<string>(key);
        }

        /// <summary>
        /// Reads a property value from the Farms property bag with elevated priveleges
        /// </summary>
        /// <typeparam name="T">T can be a string, DateTime, or an int</typeparam>
        public static T GetFarmProperty<T>(string key)
        {
            T value = default(T);
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                var farmProperties = SPFarm.Local.Properties;
                if (farmProperties.ContainsKey(key))
                    value = (T)farmProperties[key];
            });
            return value;
        }

        #endregion
    }
}
